﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace NeuralLib
{
    public class BayesTrainer
    {
        private double[] _classes;
        private int _classesCount;
        private double[] _classesProbabilities;
        private Feature[][] _features;

        public double Compute(double[] input)
        {
            var posterior = new double[_classesCount];

            for (var i = 0; i < _classesCount; i++)
            {
                posterior[i] = _classesProbabilities[i];
                for (var j = 0; j < _features.Count(); j++)
                {
                    var prob = _features[j][i].GetProbability(input[j]);
                    posterior[i] *= prob;
                }
            }

            return _classes[Array.IndexOf(posterior, posterior.Max())];
        }

        public void Run(double[][] input, double[] output)
        {
            _classes = output.Distinct().ToArray();
            _classesCount = _classes.Count();
            var featureCount = input[0].Count();
            var samplesCount = input.GetLength(0);
            _features = new Feature[featureCount][];

            for (var i = 0; i < featureCount; i++)
                _features[i] = new Feature[_classesCount];

            _classesProbabilities = new double[_classesCount];

            for (var j = 0; j < featureCount; j++)
            {
               
                    for (var i = 0; i < _classesCount; i++)
                        _features[j][i] = new ContinuousFeature();
                

                for (var i = 0; i < samplesCount; i++)
                {
                    var classID = Array.IndexOf(_classes, output[i]);
                    _classesProbabilities[classID]++;
                    _features[j][classID].Add(input[i][j]);
                }

                for (var i = 0; i < _classesCount; i++)
                {
                    _classesProbabilities[i] /= _classesProbabilities.Sum();
                    _features[j][i].Compute();
                }
            }
        }
    }

    public class ContinuousFeature : Feature
    {
        public double Mean;

        public double Variance;

        private readonly List<double> Values;

        public ContinuousFeature()
        {
            Values = new List<double>();
        }

        public override void Add(object value)
        {
            Values.Add((double)value);
        }

        public override void Compute()
        {
            Mean = Values.Average();
            foreach (var item in Values)
                Variance += item * item;
            Variance = Variance / Values.Count - Mean * Mean;
        }

        public override double GetProbability(object value)
        {
            var diff = (double)value - Mean;
            var exp = Math.Exp(-diff * diff / (2.0 * Variance));
            return (1.0 / (2.0 * Math.PI * Variance)) * exp;
        }
    }

    public abstract class Feature
    {
        public abstract void Add(object value);

        public abstract void Compute();

        public abstract double GetProbability(object value);
    }

    public class RangeFeature : Feature
    {
        private List<string> _classes;
        private double[] _probabilities;
        private readonly List<string> _values;

        public RangeFeature()
        {
            _values = new List<string>();
        }

        public override void Add(object value)
        {
            _values.Add((string)value);
        }

        public override void Compute()
        {
            _classes = _values.Distinct().ToList();
            _probabilities = new double[_classes.Count];
            for (var i = 0; i < _classes.Count(); i++)
            {
                _probabilities[i] = (double)_values.FindAll(item => item == _classes[i]).Count / _values.Count;
            }
        }

        public override double GetProbability(object value)
        {
            return _probabilities[_classes.IndexOf((string)value)];
        }
    }
}